package VideoRent.Caja;

import java.util.*;
import java.io.*;

import VideoRent.*;
import VideoRent.Articulo.*;
import VideoRent.Cliente.*;
import VideoRent.Enum.*;
import VideoRent.Alquiler.*;

public class Cuenta{
    //Cedula del cliente asociado a la cuenta
    private int cedula;
    //Contexto en el cual se abre la cuenta
    private ElementosTienda contexto;
    //Apuntador al cliente asociado, cuando aplique
    private ClienteAsociado cliente;
    //Artículos que están listados para ser comprados
    private ArrayList<Articulo> comprados;
    //Artículos que están listados para ser alquilados
    private ArrayList<Articulo> alquilados;
    //Artículos que están listados para ser devueltos y deben pagar multa
    private ArrayList<Alquiler> devueltos;
    //Variable interna que indica si el cliente especificado es asociado
    private boolean esSocio;

    /**
     * Constructor de la clase utilizando una cédula
     */
    public Cuenta(int cedula, ElementosTienda contexto){
        this.cedula = cedula;
        this.contexto = contexto;
        ClienteAsociado c = this.contexto.buscarClienteAsociado(this.cedula);
        if (c == null){
            this.esSocio = false;
        }
        else{
            this.esSocio = true;
            this.cliente = c;
        }
        comprados = new ArrayList<Articulo>();
        alquilados = new ArrayList<Articulo>();
        devueltos = new ArrayList<Alquiler>();
    }

    /**
     * Constructor de la clase utilizando un codigo
     */
    public Cuenta(String codigo, ElementosTienda contexto){
        this.contexto = contexto;
        ClienteAsociado c = this.contexto.buscarClienteAsociado(codigo);
        if (c == null){
            this.esSocio = false;
            this.cedula = -1;
        }else{
            this.esSocio = true;
            this.cliente = c;
        }
        comprados = new ArrayList<Articulo>();
        alquilados = new ArrayList<Articulo>();
        devueltos = new ArrayList<Alquiler>();
    }

    /**
     * Método para agregar un artículo a las compras
     * @param codigoArticulo Codigo del artículo que se desea comprar
     * @param escritor Buffer en el cual se debe escribir las acciones del empleado
     * @param contexto Tienda en la cual se debe realizar la operacion para
     */
    public void agregarCompra(String codigoArticulo, BufferedWriter escritor)
                    throws VideoRentAccionException{

        //Declaración de variables
        String mensaje;
        Articulo compra;

        //Se verifica que se esté usando una cuenta válida
        if(this.cedula == -1){
            try{
                escritor.write("i & " + this.cedula + " & 7");
                escritor.newLine();
            }catch (IOException e){
                throw new VideoRentAccionException("No se pudo escribir la falla: " + e.getMessage());
            }
            return;
        }

        //Se verifica que este artículo está disponible para ser comprado y se agrega
        //a la cuenta
        compra = contexto.buscarArticulo(codigoArticulo);
        if((compra == null) || (compra.getCantidad() == 0)){
            try{
                if (this.esSocio){
                    mensaje = "i & " + this.cliente.getCodigo() + " & 1";
                }else{
                    mensaje = "i & " + this.cedula + " & 1";
                }
                escritor.write(mensaje);
                escritor.newLine();
            }catch (IOException e){
                throw new VideoRentAccionException("No se pudo escribir la falla: " + e.getMessage());
            }
            return;            
        }

        this.comprados.add(compra);
        compra.setCantidad(compra.getCantidad() - 1);
    }

    /**
     * Método para agregar el alquiler de un artículo a la cuenta actual
     *
     * @param codigoA Código del artículo a alquilar
     * @param escritor Buffer al cual se escribirán las acciones a realizar
     */
    public void agregarAlquiler(String codigoA, BufferedWriter escritor, Calendar diaActual)
                    throws VideoRentAccionException{

        //Declaración de variables
        String mensaje;
        Articulo alquiler;
        int cantidadArticulo;
        Alquiler al;
 
        //Se valida que la cuenta esté autorizada a hacer la accion
        //Primero se valida que sea socio
        if(!this.esSocio){
            try{
                escritor.write("i & " + this.cedula + " & 2");
                escritor.newLine();
            }catch (IOException e){
                throw new VideoRentAccionException("No se pudo escribir que el cliente " +
                                                    "no está autorizado para alquilar: " + e.getMessage());
            }
            return;
        }

        //Luego se valida que no esté suspendido
        if(this.cliente.getEstado() == EstadoCliente.SUSPENDIDO){
            try{
                escritor.write("i & " + this.cliente.getCodigo() + " & 6");
                escritor.newLine();
            }catch (IOException e){
                throw new VideoRentAccionException("No se pudo escribir que el cliente está suspendido: " 
                                                    + e.getMessage());
            }
            return;
        }

        //Se verifica que el artículo esté disponible
        alquiler = contexto.buscarArticulo(codigoA);
        if((alquiler == null) || (alquiler.getCantidad() == 0)){
            try{
                escritor.write("i & " + this.cliente.getCodigo() + " & 1");
                escritor.newLine();
            }catch (IOException e){
                throw new VideoRentAccionException("No se pudo escribir la falla: " + e.getMessage());
            }
            return;            
        }

        //Se verifica que el cliente pueda rentar el artículo
        cantidadArticulo = 0;

        if(alquiler instanceof ArticuloAudiovisual){
            for(Alquiler a : this.cliente.getArticulosAlquilados()){
                if(a.getArticulo() instanceof ArticuloAudiovisual)
                    cantidadArticulo ++;
            }
            if(((this.cliente.getTipoMembresia() == Membresia.BASICA) && cantidadArticulo >= 5) ||
                ((this.cliente.getTipoMembresia() == Membresia.PREMIUM) && cantidadArticulo >= 10)){
                try{
                    escritor.write("i & " + this.cliente.getCodigo() + " & 2");
                    escritor.newLine();
                }catch (IOException e){
                    throw new VideoRentAccionException("No se pudo escribir la falla: " + e.getMessage());
                }
                return;
            }
        }else{
            for(Alquiler a : this.cliente.getArticulosAlquilados()){
                if(a.getArticulo() instanceof ArticuloInteractivo)
                    cantidadArticulo ++;
            }
            if(((this.cliente.getTipoMembresia() == Membresia.BASICA) && cantidadArticulo >= 3) ||
                ((this.cliente.getTipoMembresia() == Membresia.PREMIUM) && cantidadArticulo >= 10)){
                try{
                    escritor.write("i & " + this.cliente.getCodigo() + " & 2");
                    escritor.newLine();
                }catch (IOException e){
                    throw new VideoRentAccionException("No se pudo escribir la falla: " + e.getMessage());
                }
                return;
            }
        }

        //Se procede a rentar el artículo
        this.alquilados.add(alquiler);
        alquiler.setCantidad(alquiler.getCantidad() - 1);
    }

    /**
     * Método para agregar un artículo a la lista de devoluciones
     *
     * @param codigoA Código del artículo a devolver
     * @param escritor Buffer donde se escriben las acciones a realizar
     * @param buscar Indica si el artículo debe ser buscado
     * @param diaActual Fecha a partir de la cual se debe calcular la multa
     */
    public void devolverArticulo(String codigoA, BufferedWriter escritor, boolean buscar, Calendar diaActual)
                    throws VideoRentAccionException{
    
        //Declaración de variables
        String mensaje;
        Articulo art;
        Alquiler alqCliente = null;

        //Se valida que la cuenta esté autorizada a hacer la accion
        if(!this.esSocio){
            try{
                escritor.write("i & " + this.cedula + " & 2");
                escritor.newLine();
            }catch (IOException e){
                throw new VideoRentAccionException("No se pudo escribir la falla: " + e.getMessage());
            }
            return;
        }

        //Se verifica que el artículo exista en la tienda
        art = this.contexto.buscarArticulo(codigoA);
        if(art == null){
            try{
                escritor.write("i & " + this.cliente.getCodigo() + " & 1");
                escritor.newLine();
            }catch (IOException e){
                throw new VideoRentAccionException("No se pudo escribir la falla: " + e.getMessage());
            }
            return;
        }

        //Se verifica que el artículo que se está devolviendo haya sido alquilado por el cliente
        for(Alquiler alq : cliente.getArticulosAlquilados()){
            if (art.equals(alq.getArticulo())){
                alqCliente = alq;
                break;
            }
        }
        if(alqCliente == null){
            try{
                escritor.write("i & " + this.cliente.getCodigo() + " & 1");
                escritor.newLine();
            }catch (IOException e){
                throw new VideoRentAccionException("No se pudo escribir la falla: " + e.getMessage());
            }
            return;
        }

        //Se calcula si el cliente debe pagar una multa por el artículo
        //Si no la debe pagar, se marca el artículo como devuelto
        if(!(diaActual.before(alqCliente.getFechaMaximaRetorno()))){
            this.devueltos.add(alqCliente);
        }else{
            art.setCantidad(art.getCantidad() + 1);
            this.cliente.getArticulosAlquilados().remove(alqCliente);
        }
        if(buscar){
            try{
                escritor.write("b & " + this.cliente.getCodigo() + " & " + art.getCodigo());
                escritor.newLine();
            }catch (IOException e){
                throw new VideoRentAccionException("No se pudo escribir la falla: " + e.getMessage());
            }
        }
    }

    /**
     * Método para realizar el pago de la cuenta
     *
     * @param monto Monto entregado por el cliente
     * @param escritorE Buffer para escribir las acciones del empleado
     * @param escritorF Buffer para escribir las facturas
     * @param diaActual Dia en que se esta ejecutando la cuenta
     */
    public void pagar(int monto, BufferedWriter escritorE, BufferedWriter escritorF, Calendar diaActual)
                    throws VideoRentAccionException{

        //Declaracion de variables
        int montoCancelar = 0, multa, codError;
        String mensaje;
        Alquiler alq;

        //Se calcula el monto total a cancelar
        for(Articulo a : this.comprados){
            montoCancelar += a.getPrecioVenta();
        }
        for(Articulo a : this.alquilados){
            montoCancelar += a.getMontoBase();
        }
        for(Alquiler a : this.devueltos){
            multa = (int)Math.floor(((diaActual.getTimeInMillis() - a.getFechaMaximaRetorno().getTimeInMillis()) / (1000 * 60 * 60 * 24))) * 
                    a.getArticulo().getMontoMulta();
            a.setMulta(multa);
            montoCancelar += multa;
        }

        //Si el monto entregado no es válido, anular la transacción
        if(monto != montoCancelar){

            //Se reponen los artículos que se iban a comprar
            for(Articulo a : this.comprados){
                a.setCantidad(a.getCantidad() + 1);
            }

            //Se reponen los artículos que iban a ser rentados
            for(Articulo a : this.alquilados){
                a.setCantidad(a.getCantidad() + 1);
            }

            //Se imprime el error
            try{
                codError = montoCancelar < monto ? 4 : 3;
                if(this.esSocio){
                    mensaje = "i & " + this.cliente.getCodigo() + " & " + codError;
                }else{
                    mensaje = "i & " + this.cedula + " & " + codError;
                }
                escritorE.write(mensaje);
                escritorE.newLine();
            }catch (IOException e){
                throw new VideoRentAccionException("No se pudo escribir la falla: " + e.getMessage());
            }
            return;
        }

        //Si el monto si es válido, se procede con la operación

        try{
            //Se crea el inicio de la factura
            escritorF.write("---------------------------------------------------------- " + diaActual.get(Calendar.DAY_OF_MONTH) + 
                            "/" + diaActual.get(Calendar.MONTH) + "/" + diaActual.get(Calendar.YEAR));
            escritorF.newLine();
            escritorF.write("DATOS DEL CLIENTE");
            escritorF.newLine();
            if(this.esSocio){
                escritorF.write("TIPO DE CLIENTE: " + this.cliente.getTipoMembresia());
                escritorF.newLine();
                escritorF.write("NOMBRE: " + this.cliente.getNombre());
                escritorF.newLine();
                escritorF.write("APELLIDO: " + this.cliente.getApellido());
                escritorF.newLine();
                escritorF.write("CI: " + this.cliente.getCedula());
                escritorF.newLine();
                escritorF.write("CODIGO: " + this.cliente.getCodigo());
                escritorF.newLine();
            }else{
                escritorF.write("TIPO DE CLIENTE: NO MIEMBRO");
                escritorF.newLine();
                escritorF.write("CI: " + this.cedula);
                escritorF.newLine();
            }
            escritorF.write("CONSUMO:");
            escritorF.newLine();
            escritorF.write("-------------- COMPRAS -------------");
            escritorF.newLine();

            //Se imprimen los artículos comprados
            for(Articulo a : this.comprados){
                escritorF.write(a.getCodigo() + "   " + a.getNombre() + "   " + a.getPrecioVenta());
                escritorF.newLine();
            }

            escritorF.write("-------------- ALQUILERES -------------");
            escritorF.newLine();
            
            //Se asignan e imprimen los alquileres
            for(Articulo a : this.alquilados){
                alq = new Alquiler(diaActual, this.cliente, a);
                this.cliente.getArticulosAlquilados().add(alq);

                escritorF.write(a.getCodigo() + "   " + a.getNombre() + "   " + a.getMontoBase());
                escritorF.newLine();
            }

            escritorF.write("-------------- DEVOLUCIONES CON MULTA -------------");
            escritorF.newLine();

            //Se procesan e imprimen las devoluciones que se pagaron con multa
            for(Alquiler a : this.devueltos){
                this.cliente.getArticulosAlquilados().remove(a);
                a.getArticulo().setCantidad(a.getArticulo().getCantidad() + 1);

                escritorF.write(a.getArticulo().getCodigo() + "   " + a.getArticulo().getNombre() + "   " + a.getMulta());
                escritorF.newLine();
            }

            escritorF.write("--------------------------------- MONTO CANCELADO: " + monto + "\n");
        } catch (IOException e){
            throw new VideoRentAccionException("No se pudo escribir la factura: " + e.getMessage());
        }

        //Se imprime la acción del empleado
        try{
            if(this.esSocio){
                mensaje = "f & " + this.cliente.getCodigo() + " & " + monto;
            }else{
                mensaje = "f & " + this.cedula + " & " + monto;
            }
            escritorE.write(mensaje);
            escritorE.newLine();
        }catch (IOException e){
            throw new VideoRentAccionException("No se pudo escribir la acción del empleado: " + e.getMessage());
        }
    }

    //Lista de 'getters' de la clase

    public ClienteAsociado getCliente(){return this.cliente;}
    public int getCedula(){return this.cedula;}
    public ArrayList<Articulo> getComprados(){return this.comprados;}
    public ArrayList<Articulo> getAlquilados(){return this.alquilados;}
    public ArrayList<Alquiler> getDevueltos(){return this.devueltos;}

    //Lista de 'setters' de la clase

    public void setComprados(ArrayList<Articulo> a){this.comprados = a;}
    public void setAlquilados(ArrayList<Articulo> a){this.alquilados = a;}
    public void setDevueltos(ArrayList<Alquiler> a){this.devueltos = a;}
}